/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 d3leaf@126.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.mapperdb.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import javax.annotation.Resource;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectKey;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.Update;
import org.apache.ibatis.annotations.UpdateProvider;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import com.mapperdb.builder.annotation.ResultBuilder;
import com.mapperdb.core.Assert;
import com.mapperdb.db.annotation.Transient;
import com.mapperdb.joor.Reflect;

public class MapperUtils {
	public static final String ATTR_READ_REGEX = "^(get|is)([A-Z]+)([A-Za-z0-9_]*)";

	private static final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();
	private static final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();

	static {
		sqlAnnotationTypes.add(Select.class);
		sqlAnnotationTypes.add(Insert.class);
		sqlAnnotationTypes.add(Update.class);
		sqlAnnotationTypes.add(Delete.class);

		sqlProviderAnnotationTypes.add(SelectProvider.class);
		sqlProviderAnnotationTypes.add(InsertProvider.class);
		sqlProviderAnnotationTypes.add(UpdateProvider.class);
		sqlProviderAnnotationTypes.add(DeleteProvider.class);
	}

	public static Class<? extends Annotation> chooseAnnotationType(Method method, Set<Class<? extends Annotation>> types) {
		for (Class<? extends Annotation> type : types) {
			Annotation annotation = method.getAnnotation(type);
			if (annotation != null) { return type; }
		}
		return null;
	}

	public static Class<? extends Annotation> getSqlAnnotationType(Method method) {
		return chooseAnnotationType(method, sqlAnnotationTypes);
	}

	public static Class<? extends Annotation> getSqlProviderAnnotationType(Method method) {
		return chooseAnnotationType(method, sqlProviderAnnotationTypes);
	}

	/**
	 * 重新设置SqlSource，同时判断如果是Jdbc3KeyGenerator，就设置为MultipleJdbc3KeyGenerator
	 *
	 * @param ms
	 * @param sqlSource
	 */
	public static void setSqlSource(MappedStatement ms, SqlSource sqlSource) {
		MetaObject msObject = SystemMetaObject.forObject(ms);
		msObject.setValue("sqlSource", sqlSource);
	}

	/**
	 * 设置返回值类型
	 *
	 * @param ms
	 * @param type
	 */
	public static void setResultType(MappedStatement ms, Class<?> type) {
		ResultMap resultMap = ms.getResultMaps().get(0);
		MetaObject metaObject = SystemMetaObject.forObject(resultMap);
		metaObject.setValue("type", type);
	}

	public static SqlSource createSqlSource(MappedStatement ms, String sql) {
		return ms.getLang().createSqlSource(ms.getConfiguration(), "<script>\n\t" + sql + "</script>", null);
	}

	public static void setKeyGenerator(MappedStatement ms, KeyGenerator keyGenerator, SelectKey selectKey) {
		MetaObject msObject = SystemMetaObject.forObject(ms);
		msObject.setValue("keyProperties", delimitedStringtoArray(selectKey.keyProperty()));
		msObject.setValue("keyGenerator", keyGenerator);
		msObject.setValue("keyColumns", null);
	}

	/**
	 * 获取返回值类型 - 实体类型
	 *
	 * @param ms
	 * @return
	 */
	public static Class<?> getGenericType(String msId) {
		Class<?> mapperClass = getMapperClass(msId);
		Type[] types = mapperClass.getGenericInterfaces();
		for (Type type : types) {
			if (type instanceof ParameterizedType) {
				ParameterizedType t = (ParameterizedType) type;
				return (Class<?>) t.getActualTypeArguments()[0];
			}
		}
		throw new RuntimeException("无法获取Mapper<T>泛型类型:" + msId);
	}

	/**
	 * 根据msId获取接口类
	 *
	 * @param msId
	 * @return
	 * @throws ClassNotFoundException
	 */
	public static Class<?> getMapperClass(String msId) {
		Assert.state(msId.indexOf(".") != -1, "当前 MappedStatement 的 id = " + msId + ",不符合 MappedStatement 的规则!");
		String mapperClassName = msId.substring(0, msId.lastIndexOf("."));
		try {
			return Class.forName(mapperClassName);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("当前 mapper 不存在:" + mapperClassName, e);
		}
	}

	/**
	 * 根据 msId 获取 接口 方法名
	 *
	 * @param msId
	 * @return
	 */
	public static String getName(String msId) {
		if (msId.indexOf(".") == -1) { throw new RuntimeException("当前 MappedStatement 的 id = " + msId
				+ " , 不符合 MappedStatement 的规则!"); }
		return msId.substring(msId.lastIndexOf(".") + 1);
	}

	/**
	 * 获取返回值类型 - 实体类型
	 *
	 * @param ms
	 * @return
	 */
	public static Class<?> getGenericInterfaces0(Class<?> mapperClass) {
		Type[] types = mapperClass.getGenericInterfaces();
		if (types != null) {
			for (Type type : types) {
				if (type instanceof ParameterizedType) {
					ParameterizedType t = (ParameterizedType) type;
					return (Class<?>) t.getActualTypeArguments()[0];
				}
			}
		}
		return null;
	}

	public static String[] delimitedStringtoArray(String in) {
		if (in == null || in.trim().length() == 0) {
			return null;
		} else {
			if (in.indexOf(",") == -1) return new String[] { in };

			List<String> list = new ArrayList<String>();
			int endIndex = -1;
			int beginIndex = 0;
			while ((endIndex = in.indexOf(",", beginIndex)) != -1) {
				String t = in.substring(beginIndex, endIndex);
				if (StrKit.notBlank(t)) list.add(in.substring(beginIndex, endIndex));
				beginIndex = endIndex + 1;
			}
			return in.split(",");
		}
	}

	public static String getTableName(Class<?> entityClass) {
		Resource resource = entityClass.getAnnotation(Resource.class);
		String tableName = entityClass.getSimpleName();
		if (resource != null) {
			String name = resource.name();
			if (!(name == null || name.isEmpty())) {
				tableName = name;
			}
		}
		return tableName;
	}

	public static Result[] getResults(Class<?> entityClass) {
		return ResultBuilder.use(readAttrs(entityClass)).getResults();
	}

	public static boolean isAttr(Method m) {
		if (Modifier.isStatic(m.getModifiers())// static
				|| hasTransient(m)// Transient
				|| m.getParameterTypes().length != 0// param
				|| m.getReturnType() == Void.TYPE// void
				|| m.getName().equals("getClass")// Object --> getClass()
				|| !Pattern.matches(ATTR_READ_REGEX, m.getName())) { // ( get*** or is*** )
			return false;
		}
		return true;
	}

	static boolean hasTransient(Method m) {
		boolean hasTransient = false;
		Class<? extends Annotation> transientClass = Transient.class;
		try {
			if (m.getAnnotation(transientClass) == null) {
				// java.beans.Transient JDK7 或JDK7 以上才有
				transientClass = Reflect.on("java.beans.Transient").get();
				if (m.getAnnotation(transientClass) != null) hasTransient = true;
			} else {
				hasTransient = true;
			}
		} catch (Exception e) {
			// ignore
		}
		return hasTransient;
	}

	public static List<Method> readAttrs(Class<?> entityClass) {
		List<Method> list = new ArrayList<Method>();

		Method[] methods = entityClass.getMethods();
		// 筛选 get 属性
		for (Method m : methods) {
			if (isAttr(m)) {// ( get*** or is*** )
				list.add(m);
			}
		}

		return list;
	}

	public static SelectKey parseSelectKey(Class<?> type) {
		try {
			List<Method> attrs = readAttrs(type);
			for (Method method : attrs) {
				SelectKey selectKey = method.getAnnotation(SelectKey.class);
				if (selectKey != null) return selectKey;
			}
		} catch (Exception e) {
			// ignore exception
		}
		return null;
	}
}